/*
 * File:    ClientChannelImpl.java
 * Created: 19-May-2007
 * Version: $Id$
 * 
 * COPYRIGHT (C) 2007, Bitgate Software, LLC.  All Rights Reserved.
 *
 * software@bitgatesoftware.com
 */

package com.bitgate.util.socket;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SocketChannel;

import com.bitgate.util.socket.AbstractSocketProperties;

/**
 * This is an abstract implementation of the {@link ClientChannel}, which is responsible for sending and receiving
 * data to and from a connected client.
 * <p/>
 * TODO: Fix the code so that when adding read/write data, it doesn't inflate the write buffer size improperly.  Right
 * now, it magnifies way beyond a manageable number!
 * 
 * @author kenji
 */
public abstract class AbstractClientChannel extends AbstractSocketProperties implements ClientChannel
{
    protected final SocketChannel wrappedChannel;
    protected final long createTime;
    protected byte[] writeData;
    private WriteFuture writeFuture;
    private volatile boolean closed = false;
    
    private static final ClosedChannelException ccException = new ClosedChannelException();

    public AbstractClientChannel(SocketChannel wrappedChannel)
    {
        this.wrappedChannel = wrappedChannel;
        this.createTime = System.currentTimeMillis();
        this.writeData = null;
        this.writeFuture = new AbstractWriteFuture();
    }

    public long getCreateTime()
    {
    	return this.createTime;
    }

    public SocketChannel getSocketChannel()
    {
        return this.wrappedChannel;
    }
    
    public void setIndicatorClosed()
    {
    	this.closed = true;
    }

    public void close() throws IOException
    {
    	setIndicatorClosed();
        this.wrappedChannel.close();	
    }

    public boolean isOpen()
    {
        return(!closed);
    }

    public int read(ByteBuffer byteBuffer) throws IOException, ClosedChannelException
    {
    	if (closed) {
    		throw ccException;
    	}
    	
		int bytesRead = this.wrappedChannel.read(byteBuffer);
	
		SocketStatistics.getDefault().increaseReads(bytesRead);
		
		return bytesRead;
    }

    public ClientChannel write(byte[] data) throws ClosedChannelException
    {
    	if (closed) {
    		throw ccException;
    	}
    	
    	if (data == null) {
    		return this;
    	}
    	
    	if (writeData != null) {
        	byte[] outData = new byte[writeData.length + data.length];
        	
        	System.arraycopy(writeData, 0, outData, 0, writeData.length);
        	System.arraycopy(data, 0, outData, writeData.length, data.length);
    		
        	writeData = outData;
        	outData = null;
    	} else {
    		writeData = data;
    	}
    	
    	return this;
    }
    
    public boolean signalWrite() throws IOException
    {	
    	int bytesLeft = writeData.length;
    	ByteBuffer outBuffer = ByteBuffer.wrap(writeData);
		int bytesWritten = this.wrappedChannel.write(outBuffer);
	
		SocketStatistics.getDefault().increaseWrites(bytesWritten);

		writeFuture.increaseByteCount(bytesWritten);
		
		if (bytesWritten == bytesLeft) {
			outBuffer = null;
			writeData = null;
			
			// Calls the "onComplete" function of the write future after writing completes.
			writeFuture.onComplete();
			writeFuture.clearByteCount();
			
			return false;
		}
		
		byte[] remainingData = new byte[bytesLeft - bytesWritten];
		
		System.arraycopy(writeData, bytesWritten, remainingData, 0, (bytesLeft - bytesWritten));
		
		writeData = remainingData;
		remainingData = null;
		
		return true;
    }
    
    public void setWriteFuture(WriteFuture wFuture)
    {
    	writeFuture = wFuture;
    }
    
    public abstract boolean isSecure();

}